//
// Created by Gao Shihao on 2023/8/31.
//

#ifndef PLC2LLVM_STRINGTYPE_H
#define PLC2LLVM_STRINGTYPE_H

#include <plc2llvm/TypeSystem/BasicType.h>

namespace plcst {


    template<TypeKind Kind, int size>
    class StringType : public BasicType {
    public:


        // constructor
        explicit StringType(std::string&& name): BasicType(std::move(name)), initValue() {}

        StringType(std::string&& name, std::string&& i): BasicType(std::move(name)), initValue(std::move(i)) {}

        explicit StringType(StringType<Kind, size>* another) : BasicType(another) { }

        [[nodiscard]] TypeKind getTypeKind() const override;

        [[nodiscard]] int getNBits() const override;

        virtual TypeMsg typeSynthesisForBinaryOperator(ExpressionOperator op, std::shared_ptr<Type> another) override{
            return {nullptr, 2}; // TODO
        }

        virtual TypeMsg typeSynthesisForUnaryOperator(ExpressionOperator op) override {
            return {nullptr, 2};// TODO
        }

    private:
        std::string initValue;
        static inline int nbits = size;
        static inline TypeKind kind = Kind;

    public:
        virtual TypeMsg getLargerType(std::shared_ptr<Type> another) override {
            auto thisShared = ScopeManager::getScopeManager().getGlobalScope()->find<Type>(this->getTypeName());

            auto anotherTypeKind = another->getTypeKind();
            auto thisTypeKind = this->getTypeKind();
            if(thisTypeKind == anotherTypeKind){ // this = another
                return {thisShared, 0};
            }

            auto typeMachine = TypeMachine::getTypeMachine();
            if(!typeMachine.isString(anotherTypeKind)){ // another is not string type
                return {nullptr, 2};
            }

            // this and another are all wstring or wchar
            if(typeMachine.isWString(thisTypeKind) && typeMachine.isWString(anotherTypeKind)){
                if(thisTypeKind == TypeKind::WSTRING){
                    return {thisShared, 0};
                }else{
                    return {another, 0};
                }
            }

            // this and another are all string or char
            if((!typeMachine.isWString(thisTypeKind)) && (!typeMachine.isWString(anotherTypeKind))){
                if(thisTypeKind == TypeKind::STRING){
                    return {thisShared, 0};
                }else{
                    return {another, 0};
                }
            }
            return {nullptr, 2};
        }

        virtual llvm::Value* castTo(std::shared_ptr<Type> destTy, llvm::Value* src, llvm::IRBuilder<>* builder) override {
            throw SemanticError("string type cast to be implemented");
            return nullptr;
        }

        virtual std::shared_ptr<Type> clone() override {
            return std::make_shared<StringType<Kind, size>>(this);
        }
    };

    template<TypeKind Kind, int size>
    TypeKind StringType<Kind, size>::getTypeKind() const {
        return kind;
    }

    template<TypeKind Kind, int size>
    int StringType<Kind, size>::getNBits() const {
        return StringType<Kind, size>::nbits;
    }
}



#endif //PLC2LLVM_STRINGTYPE_H
